package org.visionlibrary.image.filters.labeling;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import javax.media.jai.TiledImage;

import org.visionlibrary.image.acquisition.ImageFactory;
import org.visionlibrary.image.colormodel.RGBConverter;
import org.visionlibrary.image.filters.adjustement.BackgroundClear;
import org.visionlibrary.image.geomtric.model.Mask2d;
import org.visionlibrary.image.geomtric.model.Point2d;
import org.visionlibrary.image.geomtric.util.Pair;
import org.visionlibrary.image.model.AbstractFilter;
import org.visionlibrary.image.model.FilterTools;


//Wyznacza ilosc klas oraz generuje obraz w ktorym barwnie zostaja one zaznaczone
//dla celow bardziej debug niz faktycznego uzycia
public class RegionCounterColorer extends AbstractFilter {
	public static final int[] DEFAULT_BACKGROUND = new int[] { 255, 255, 255 };
	private Mask2d mask = null;

	private int[] background; // TODO mylnie przyjmujemy jaki jest color space

	private int xLength = 0;
	private int yLength = 0;

	private int minSize = 0;
	private int maxSize = 0;

	private int found = 0;

	private Set<Point2d> pts = new HashSet<Point2d>();
	private ArrayList<Point2d> stack = new ArrayList<Point2d>();
	private ArrayList<Pair<Integer, ArrayList<Point2d>>> regions = new ArrayList<Pair<Integer, ArrayList<Point2d>>>();

	public RegionCounterColorer(int minimalRegionSize, int maximalRegionSize,
			int[] backgroundColor) {
		this.minSize = minimalRegionSize;
		this.maxSize = maximalRegionSize;
		this.background = backgroundColor;
	}

	public RegionCounterColorer(int minimalRegionSize, int maximalRegionSize) {
		this(minimalRegionSize, maximalRegionSize,
				RegionCounterColorer.DEFAULT_BACKGROUND);
	}

	public RegionCounterColorer() {
		this(0, Integer.MAX_VALUE, RegionCounterColorer.DEFAULT_BACKGROUND);
	}

	private void clearMem() {
		stack.clear();
		pts.clear();
	}

	private boolean checkSize(int minSize, int maxSize, int size) {
		if (minSize <= size && size <= maxSize) {
			return true;
		}

		return false;
	}

	private int grow() {
		int size = 1;
		do {
			Set<Point2d> newPts = new HashSet<Point2d>();
			for (Point2d p : pts) {
				int x = p.getXAsInt();
				int y = p.getYAsInt();

				if (x > 0 && mask.get(x - 1, y)) {
					mask.unSet(x - 1, y);
					stack.add(new Point2d(x - 1, y));
					++size;

					if (checkSize(0, maxSize - 1, size))
						newPts.add(new Point2d(x - 1, y));
					else
						return size;
				}

				if (x < xLength - 1 && mask.get(x + 1, y)) {
					mask.unSet(x + 1, y);
					stack.add(new Point2d(x + 1, y));
					++size;

					if (checkSize(0, maxSize - 1, size))
						newPts.add(new Point2d(x + 1, y));
					else
						return size;
				}

				if (y > 0 && mask.get(x, y - 1)) {
					mask.unSet(x, y - 1);
					stack.add(new Point2d(x, y - 1));
					++size;

					if (checkSize(0, maxSize - 1, size))
						newPts.add(new Point2d(x, y - 1));
					else
						return size;
				}

				if (y < yLength - 1 && mask.get(x, y + 1)) {
					mask.unSet(x, y + 1);
					stack.add(new Point2d(x, y + 1));
					++size;

					if (checkSize(0, maxSize - 1, size))
						newPts.add(new Point2d(x, y + 1));
					else
						return size;
				}
			}
			pts = newPts;
		} while (!pts.isEmpty());
		return size;
	}

	public int getRegionNumber() {
		return found;
	}

	@Override
	public TiledImage applyFilter(TiledImage src, TiledImage dest) {
		if (null == src)
			throw new NullPointerException("Source image is null.");

		if (null == dest)
			dest = (new ImageFactory()).createCompatibleImage(src);

		this.mask = Mask2d.createForegroundMask(src, background);
		this.xLength = mask.width();
		this.yLength = mask.height();
		this.regions.clear();

		for (int x = 0; x < mask.width(); ++x) {
			for (int y = 0; y < mask.height(); ++y) {
				if (mask.get(x, y)) {
					mask.unSet(x, y);
					pts.add(new Point2d(x, y));
					stack.add(new Point2d(x, y));

					int size = grow();
					if (checkSize(this.minSize, this.maxSize, size)) {
						this.found++;
						ArrayList<Point2d> tmp = new ArrayList<Point2d>();
						tmp.addAll(stack);
						regions.add(new Pair<Integer, ArrayList<Point2d>>(
								new Integer(this.found), tmp));
					}

					clearMem();
				}
			}
		}

		BackgroundClear bc = new BackgroundClear();
		bc.applyFilter(dest, null);

		int hsvStep = 360 / regions.size();

		for (int i = 0; i < regions.size(); i++) {
			for (int k = 0; k < regions.get(i).getRight().size(); k++) {
				Integer regNumber = regions.get(i).getLeft();
				Point2d point = regions.get(i).getRight().get(k);

				float[] rgb_f = (new RGBConverter())
						.convertFromTYPE_HSV(new float[] {
								hsvStep * regNumber.intValue(), 0.5f, 0.5f });

				for (int b = 0; b < dest.getNumBands(); b++)
					dest.setSample(point.getXAsInt(), point.getYAsInt(), b,
							FilterTools.norm(0, 1, 0, 255, rgb_f[b]));
			}
		}

		return dest;
	}
}
